/*-
 * Address.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 98 $
 * $LastChangedDate: 2007-02-20 15:18:26 +0000 (Tue, 20 Feb 2007) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/Address.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.sms.pdu;

import static com.googlecode.attention.sms.pdu.Codec.*;
import static com.googlecode.attention.util.SMSCharacterSet.*;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.googlecode.attention.util.Convert;
import com.googlecode.attention.util.CopiesReference;
import com.googlecode.attention.util.IntArrayBuilder;


/**
 * Address class, houses phone number and TOA value.
 *
 * @author Stein Gunnar Bakkeby
 */
public class Address extends IntArrayBuilder {

  private static final long serialVersionUID = 3417988352152616576L;
  private TOA toa;
  
  /**
   * Default constructor, creates an empty address field.
   * <BR>SMS address field consists of a maximum of 12 octets,
   * giving 1 address length octet, 1 TOA octet, and 10 address octets.
   */
  public Address() {
    super();
    this.toa = new TOA();
  }
  
  /**
   * This will create the Address object based on the address field.
   * The address field must contain all octets, meaning the address length,
   * the TOA octet, and the number octet.
   * 
   * @param addressField Hex string on the form length + toa + number
   */
  public Address(final String addressField) {
    this(Convert.hexToIntArray(addressField, 2));
  }
  
  /**
   * Constructor, creates the Address object based on the address field.
   * The address field must contain all octets, meaning the address length,
   * the TOA octet, and the number octet.
   * 
   * @param addressField Int octet array
   * @throws IllegalArgumentException If < 2 octets or > 12 octets
   */
  public Address(final int[] addressField) throws IllegalArgumentException {
//    this(addressField[0], new TOA(addressField[1]),
//        new IntArrayBuilder(addressField).substring(2).toArray());
    
    final int length = addressField.length;
    if (length < 2 || length > 12) throw new IllegalArgumentException(
        "The address field has an illegal length");
    if (length < 2) {
      toa = new TOA();
    } else {
      toa = new TOA(addressField[1]);
      if (toa.getTypeOfNumber() == TOA.TON_ALPHANUMERIC) {
        int al  = addressField[0] / 2;
        final int[] address = new int[al + 1];
        System.arraycopy(addressField, 2, address, 0, al);
        if (al % 7 == 0 || al / 7 >= 1) al++;
        set(gsmToUnicode(decode7bit(address, al, 0)));
      } else {
        for (int i = 2; i < length; i++) {
          this.append((addressField[i] & 0xF0) >> 4);
          this.append((addressField[i] & 0xF));
        }
        nibble();
      }
    }
  }
  
  /**
   * Constructor, creates the Address object based on the address field.
   * The address field must contain all octets, meaning the address length,
   * the TOA octet, and the number octet.
   * 
   * @param al The address length
   * @param toa The Type-of-Address
   * @param address The address
   */
  public Address(final int al, final TOA toa, final int[] address) {
    //this(new IntArrayBuilder(address.length + 2).append(al).append(toa.getValue()).append(address));
    this("" + Convert.intToHex(al) + Convert.intToHex(toa.getValue()) + Convert.intToHex(address));
//    
//    /* Ignores address length because it should already have been used. */
//    int length = address.length;
//    if (length > 12) throw new IllegalArgumentException(
//        "The address field has an illegal length");
//    this.toa = toa;
//    if (length > 0) { // if address
//      if (toa.getTypeOfNumber() == TOA.TON_ALPHANUMERIC) {
//        int alength = ((al & 1) == 1 ? al + 1: al) / 2;
//        if (alength % 7 == 0) alength++;
//        int[] array = decode7bit(address, alength, 0);
//        // Bug in spec? impossible to determine
//        // if 7 or 8 chars when 7 octets long
//        if (false && alength > 0 && array[alength - 1] == 0) {
//          final int[] newArray = new int[alength - 1];
//          System.arraycopy(array, 0, newArray, 0, alength - 1);
//          array = newArray;
//        }
//        set(gsmToUnicode(array));
//      } else {
//        int len = al;
//        if ((len & 1) != 0) len++;
//        final int[] newAddress = new int[len];
//        int index = 0;
//        int i;
//        int val = 0;
//        for (i = 0; i < len; i++) {
//          if ((i & 1) == 0) val = address[index++];
//          newAddress[i] = (i & 1) == 0 ? (val & 0xF0) >> 4 : val & 0xF;
//        }
//        set(newAddress);
//        nibble();
//      }
//    }
  }
    
  /**
   * This will set the TOA.
   * 
   * @param toa The Type-of-Address
   * @see #setTOA(int)
   */
  public void setTOA(final TOA toa) {
    this.toa = toa;
  }
  
  /**
   * This will set the TOA.
   * 
   * @param toa The Type-of-Address
   * @see #setTOA(TOA)
   */
  public void setTOA(final int toa) {
    this.toa = new TOA(toa);
  }
  
  /**
   * This will set the number, max length of number is 20, minimum is 0.
   * 
   * @param number The address (number)
   * @throws IllegalArgumentException If length is < 0 or > 20
   */
  @CopiesReference(ref="number")
  public void setNumber(final int[] number) throws IllegalArgumentException {
    if (number.length > 20) throw new IllegalArgumentException(
        "Address.setNumber(String): Number length > 20");
    set(number);
  }
  
  /**
   * This will set the number, max length of number is 20, minimum is 0.
   * 
   * @param number The address (number)
   * @throws IllegalArgumentException If length is < 0 or > 20
   */
  @CopiesReference(ref="number")
  public void setNumber(final String number)
      throws IllegalArgumentException {
    if (number.length() > 20) throw new IllegalArgumentException(
        "Address.setNumber(String): Number length > 20");
    if (toa != null && toa.getTypeOfNumber() == TOA.TON_ALPHANUMERIC) {
      set(Convert.stringToInt(number));
    } else {
      final Pattern pattern = Pattern.compile("[A-Za-z]");
      final Matcher matcher = pattern.matcher(number);
      if (matcher.find()) {
        set(Convert.stringToInt(number));
      } else {
        set(Convert.hexToIntArray(number, 1));
      }
    }
  }
  
  /**
   * This will return the Type-of-Address.
   * 
   * @return An object representing the TOA
   */
  public TOA getTOA() {
    return toa;
  }
  
  /**
   * This will return the address field.
   * <BR>
   * <BR>The address field consists of:
   * <BR> - The address length
   * <BR> - The Type-of-Address (TOA)
   * <BR> - The address (number)
   * 
   * @return The address field
   */
  public int[] getAddressField() {
    // This operates on one integer per number,
    // e.g. the TOA and length will be two integers.
    final int length = length();
    final IntArrayBuilder output = new IntArrayBuilder(length + 2);
    if (this.toa.getTypeOfNumber() == TOA.TON_ALPHANUMERIC) {
      final int[] number = encode7bit(unicodeToGSM(this.toArray()), 0);
      output.append((number.length) * 2);
      output.append(this.toa.getValue());
      output.append(number);
    } else {
      output.append(getNumberLength());
      output.append(this.toa.getValue());
      final IntArrayBuilder number = new IntArrayBuilder(this);
      number.nibble();
      output.append(Convert.fuse4bitInto8bit(number));
    }
    return output.toArray();
  }

  /**
   * This will return the address field in string representation.
   * <BR>
   * <BR>The address field consists of:
   * <BR> - The address length
   * <BR> - The Type-of-Address (TOA)
   * <BR> - The address (number)
   * 
   * @return The address field
   */
  public String getAddressFieldAsString() {
    return Convert.intToHex(getAddressField(), 2);
  }
  
  /**
   * This will return a String representation of the number.
   * 
   * @return A String
   */
  public String getNumber() {
    if (toa.getTypeOfNumber() == TOA.TON_ALPHANUMERIC) {
      return Convert.intToString(this.toArray());
    }
    return Convert.intToHex(this, 1);
  }
  
  /**
   * This will add a cipher to the already existing number.
   * 
   * @param cipher The cipher to add
   * @return True if total length is < 20 and <code>cipher</code> was added
   */
  public boolean addCipher(final int cipher) {
    if (endpoint < 20) {
      append(cipher);
      return true;
    }
    return false;
  }
  
  /**
   * This will return the length of the number.
   * 
   * @return The number's length
   */
  public int getNumberLength() {
    return endpoint;
  }
  
  /**
   * @serialData super, toa
   */
  @Override
  public void readExternal(final ObjectInput in) throws IOException,
                                                 ClassNotFoundException {
    super.readExternal(in);
    this.toa = new TOA(in.readInt());
  }

  /**
   * Extends the externalize of IntArrayBuilder.
   */
  @Override
  public void writeExternal(final ObjectOutput out) throws IOException {
    super.writeExternal(out);
    out.writeInt(toa.getValue());
  }
  
  /**
   * @return [TOA:&lt;int&gt;][&lt;number&gt;]
   */
  public String toString() {
    final StringBuilder sb = new StringBuilder(20);
    sb.append("[TOA:");
    sb.append(toa); // TODO fix this when fixing toa
    sb.append("][");
    sb.append(getNumber());
    sb.append(']');
    return sb.toString();
  }
}